// 版权所有2009年围棋作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 该文件包含用于调用解析器的导出入口点。

package parser

import (
	"bytes"
	"errors"
	"go/ast"
	"go/token"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
)

// 如果src！=nil，如果可能，readSource将src转换为[]字节；
// 否则它将返回一个错误。如果src==nil，则readSource返回
// 读取文件名指定的文件的结果。
// None
func readSource(filename string, src interface{}) ([]byte, error) {
	if src != nil {
		switch s := src.(type) {
		case string:
			return []byte(s), nil
		case []byte:
			return s, nil
		case *bytes.Buffer:
			// 是io.Reader，但src已以[]字节形式提供
			if s != nil {
				return s.Bytes(), nil
			}
		case io.Reader:
			return io.ReadAll(s)
		}
		return nil, errors.New("invalid source")
	}
	return os.ReadFile(filename)
}

// 模式值是一组标志（或0）。
// 它们控制解析的源代码和其他可选代码的数量
// 解析器功能。
// None
type Mode uint

const (
	PackageClauseOnly    Mode             = 1 << iota // 在package子句之后停止解析
	ImportsOnly                                       // 在导入声明后停止解析
	ParseComments                                     // 解析注释并将其添加到AST
	Trace                                             // 打印已解析产品的跟踪
	DeclarationErrors                                 // 报告声明错误
	SpuriousErrors                                    // 与AllErrors相同，用于向后兼容性
	SkipObjectResolution                              // 不将标识符解析为对象-请参阅ParseFile
	AllErrors            = SpuriousErrors             // 报告所有错误（不只是不同行上的前10个错误）
)

// ParseFile解析单个Go源文件的源代码并返回
// 对应的ast.File节点。源代码可以通过以下方式提供：
// 源文件的文件名，或通过src参数。
// None
// 如果src！=nil，ParseFile从src解析源代码，文件名为
// 仅在记录位置信息时使用。参数的类型
// 对于src，参数必须是字符串、[]字节或io.Reader。
// 如果src==nil，ParseFile将解析文件名指定的文件。
// None
// mode参数控制解析的源文本和其他文本的数量
// 可选的解析器功能。如果设置了SkipObjectResolution模式位，
// 将跳过分析的对象解析阶段，导致File.Scope，
// 未解析，所有Ident.Obj字段均为零。
// None
// 位置信息记录在文件集fset中，不能
// 无
// None
// 如果无法读取源，则返回的AST为nil，错误为
// 指示特定的故障。如果源已读取，但语法错误
// 发现错误，结果是部分AST（带有AST.Bad*节点
// 表示错误源代码的片段）。多重错误
// 通过按源位置排序的scanner.ErrorList返回。
// None
func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) {
	if fset == nil {
		panic("parser.ParseFile: no token.FileSet provided (fset == nil)")
	}

	// 获取来源
	text, err := readSource(filename, src)
	if err != nil {
		return nil, err
	}

	var p parser
	defer func() {
		if e := recover(); e != nil {
			// 如果不是紧急援助，就恢复同样的恐慌
			if _, ok := e.(bailout); !ok {
				panic(e)
			}
		}

		// 设置结果值
		if f == nil {
			// 源不是有效的Go源文件-满足
			// 解析文件API并返回有效（但）空
			// *ast.File
			f = &ast.File{
				Name:  new(ast.Ident),
				Scope: ast.NewScope(nil),
			}
		}

		p.errors.Sort()
		err = p.errors.Err()
	}()

	// 解析源
	p.init(fset, filename, text, mode)
	f = p.parseFile()

	return
}

// ParseDir调用ParseFile，用于所有文件的名称在列表中以“.go”结尾
// 路径指定的目录，并返回包名->包的映射
// AST和找到的所有包。
// None
// 如果是过滤器！=nil，仅具有fs.FileInfo条目的文件通过
// 将考虑过滤器（以“.go”结尾）。模式位被传递
// 要解析文件，请保持不变。位置信息记录在fset中，其中
// 不能为零。
// None
// 如果无法读取目录，则会显示nil映射和相应的错误
// 返回。如果发生分析错误，则会生成非nil但不完整的映射，并且
// 返回遇到的第一个错误。
// None
func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) {
	list, err := os.ReadDir(path)
	if err != nil {
		return nil, err
	}

	pkgs = make(map[string]*ast.Package)
	for _, d := range list {
		if d.IsDir() || !strings.HasSuffix(d.Name(), ".go") {
			continue
		}
		if filter != nil {
			info, err := d.Info()
			if err != nil {
				return nil, err
			}
			if !filter(info) {
				continue
			}
		}
		filename := filepath.Join(path, d.Name())
		if src, err := ParseFile(fset, filename, nil, mode); err == nil {
			name := src.Name.Name
			pkg, found := pkgs[name]
			if !found {
				pkg = &ast.Package{
					Name:  name,
					Files: make(map[string]*ast.File),
				}
				pkgs[name] = pkg
			}
			pkg.Files[filename] = src
		} else if first == nil {
			first = err
		}
	}

	return
}

// ParseExprFrom是解析表达式的便利函数。
// 参数的含义与ParseFile相同，但源必须是
// 必须是有效的Go（类型或值）表达式。具体而言，fset不得
// 零。
// None
// 如果无法读取源，则返回的AST为nil，错误为
// 指示特定的故障。如果源已读取，但语法错误
// 发现错误，结果是部分AST（带有AST.Bad*节点
// 表示错误源代码的片段）。多重错误
// 通过按源位置排序的scanner.ErrorList返回。
// None
func ParseExprFrom(fset *token.FileSet, filename string, src interface{}, mode Mode) (expr ast.Expr, err error) {
	if fset == nil {
		panic("parser.ParseExprFrom: no token.FileSet provided (fset == nil)")
	}

	// 获取来源
	text, err := readSource(filename, src)
	if err != nil {
		return nil, err
	}

	var p parser
	defer func() {
		if e := recover(); e != nil {
			// 如果不是紧急援助，就恢复同样的恐慌
			if _, ok := e.(bailout); !ok {
				panic(e)
			}
		}
		p.errors.Sort()
		err = p.errors.Err()
	}()

	// 解析表达式
	p.init(fset, filename, text, mode)
	expr = p.parseRhsOrType()

	// 如果插入了分号，则使用它；
	// 如果有更多令牌，请报告错误。
	if p.tok == token.SEMICOLON && p.lit == "\n" {
		p.next()
	}
	p.expect(token.EOF)

	return
}

// ParseExpr是一个方便的函数，用于获取表达式x的AST。
// AST中记录的位置信息未定义。使用的文件名
// 错误消息中是空字符串。
// None
// 如果发现语法错误，则结果是部分AST（带有AST.Bad*节点）
// 表示错误源代码的片段）。存在多个错误
// 通过按源位置排序的scanner.ErrorList返回。
// None
func ParseExpr(x string) (ast.Expr, error) {
	return ParseExprFrom(token.NewFileSet(), "", []byte(x), 0)
}
